﻿using System;
using System.Collections;
using System.Collections.Generic;

namespace CodepageMaintain
{
	///<summary>
	///Коллекция, автоматически преобразующая тип элемента при доступе к заданному <typeparamref name="T"/>
	///</summary>
	///<typeparam name="T"></typeparam>
	public partial class AutoCastCollection<T> : Disposable,
		IList<T>,
//ReSharper disable RedundantExtendsListEntry
		ICollection<T>,
		IEnumerable<T>,
//ReSharper restore RedundantExtendsListEntry
		IList,
//ReSharper disable RedundantExtendsListEntry
		ICollection,
		IEnumerable
//ReSharper restore RedundantExtendsListEntry
	{
		#region Nested types (structure)

		private partial class AutoCastEnumerator
		{
		}

		private partial class SafeAutoCastCollection
		{
			private partial class SafeAutoCastEnumerator
			{
			}
		}

		#endregion

		#region Частные поля класса

		private object _underlayingCollection;

		private readonly IEnumerable _asEnumerable;

		private readonly IEnumerable<T> _asEnumerableTyped;

		private readonly ICollection _asCollection;

		private readonly ICollection<T> _asCollectionTyped;

		private readonly IList _asList;

		private readonly IList<T> _asListTyped;

		private SafeAutoCastCollection _safeCast;

		#endregion

		#region Конкструктор

		///<summary>
		///Конструктор
		///</summary>
		///<param name="underlayingCollection"></param>
		public AutoCastCollection(object underlayingCollection)
		{
			if (underlayingCollection == null)
			{
				throw new ArgumentNullException("underlayingCollection");
			}

			_underlayingCollection = underlayingCollection;

			_asEnumerable = _underlayingCollection as IEnumerable;
			_asEnumerableTyped = _underlayingCollection as IEnumerable<T>;

			_asCollection = _underlayingCollection as ICollection;
			_asCollectionTyped = _underlayingCollection as ICollection<T>;

			_asList = _underlayingCollection as IList;
			_asListTyped = _underlayingCollection as IList<T>;

			if (!Enumerable && !EnumerableTyped)
			{
				_underlayingCollection = null;
				throw new ArgumentException("Underlaying collection even cannot be enumerated");
			}
		}

		#endregion

		#region Свойства класса

		///<summary>
		///Низлежащая коллекция
		///</summary>
		public object UnderlayingCollection
		{
			get { return _underlayingCollection; }
		}

		/// <summary>
		/// Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"></see>.
		/// </summary>
		/// <returns>
		/// The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"></see>.
		/// </returns>
		public int Count
		{
			get
			{
				if (CollectionTyped)
				{
					return ((ICollection<T>)this).Count;
				}
				if (Collection)
				{
					return ((ICollection)this).Count;
				}

				throw new InvalidOperationException(
					string.Format(
						"Underlaying collection dos not expose either ICollection or ICollection<{0}> interface",
						typeof(T).Name));
			}
		}

		/// <summary>
		/// Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only.
		/// </summary>
		/// <returns>
		/// true if the <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only; otherwise, false.
		/// </returns>
		public bool IsReadOnly
		{
			get
			{
				if (CollectionTyped)
				{
					return ((ICollection<T>)this).IsReadOnly;
				}
				if (List)
				{
					return ((IList)this).IsReadOnly;
				}

				throw new InvalidOperationException(
					string.Format(
						"Underlaying collection dos not expose either ICollection<{0}> or IList interface",
						typeof(T).Name));
			}
		}

		///<summary>
		///Возвращает <see cref="AutoCastCollection{T}"/>, которая при преобразовании типов
		///(для нетипизированных коллекций) в случае невозможности преобразования будет
		///возвращать null вместо эксепшина
		///</summary>
		public virtual AutoCastCollection<T> SafeCast
		{
			get
			{
				if (_safeCast == null)
				{
					//lock?
					_safeCast = new SafeAutoCastCollection(_underlayingCollection);
				}

				return _safeCast;
			}
		}

		#endregion

		#region Защищенные свойства класса

		///<summary>
		///Является ли типизированной коллекцией
		///</summary>
		protected bool CollectionTyped
		{
			get { return _asCollectionTyped != null; }
		}

		///<summary>
		///Является ли коллекцией
		///</summary>
		protected bool Collection
		{
			get { return _asCollection != null; }
		}

		///<summary>
		///Является ли типизированным листом
		///</summary>
		protected bool ListTyped
		{
			get { return _asListTyped != null; }
		}

		///<summary>
		///Является ли листом
		///</summary>
		protected bool List
		{
			get { return _asList != null; }
		}

		///<summary>
		///Является ли типизированным перечисляемым
		///</summary>
		protected bool EnumerableTyped
		{
			get { return _asEnumerableTyped != null; }
		}

		///<summary>
		///Является ли перечисляемым
		///</summary>
		protected bool Enumerable
		{
			get { return _asEnumerable != null; }
		}

		#endregion

		#region Управление коллецкией

		/// <summary>
		/// Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1"></see>.
		/// </summary>
		/// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1"></see>.</param>
		/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only.</exception>
		public void Add(T item)
		{
			if (CollectionTyped)
			{
				((ICollection<T>)this).Add(item);
			}
			else if (List)
			{
				((IList)this).Add(item);
			}
			else
			{
				throw new NotSupportedException(
					string.Format(
						"Underlaying collection does not exposes either ICollection<{0}> or IList interfaces",
						typeof(T).Name));
			}
		}

		/// <summary>
		/// Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1"></see>.
		/// </summary>
		/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only. </exception>
		public void Clear()
		{
			if (CollectionTyped)
			{
				((ICollection<T>)this).Clear();
			}
			else if (List)
			{
				((IList)this).Clear();
			}

			throw new NotSupportedException(
				string.Format(
					"Underlaying collection does not exposes either ICollection<{0}> or IList interfaces",
					typeof(T).Name));
		}

		/// <summary>
		/// Determines whether the <see cref="T:System.Collections.Generic.ICollection`1"></see> contains a specific value.
		/// </summary>
		/// <returns>
		/// true if item is found in the <see cref="T:System.Collections.Generic.ICollection`1"></see>; otherwise, false.
		/// </returns>
		/// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1"></see>.</param>
		public bool Contains(T item)
		{
			if (CollectionTyped)
			{
				return ((ICollection<T>)this).Contains(item);
			}
			if (List)
			{
				return ((IList)this).Contains(item);
			}

			throw new NotSupportedException(
				string.Format(
					"Underlaying collection does not exposes either ICollection<{0}> or IList interfaces",
					typeof(T).Name));
		}

		/// <summary>
		/// Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1"></see> to an <see cref="T:System.Array"></see>, starting at a particular <see cref="T:System.Array"></see> index.
		/// </summary>
		/// <param name="array">The one-dimensional <see cref="T:System.Array"></see> that is the destination of the elements copied from <see cref="T:System.Collections.Generic.ICollection`1"></see>. The <see cref="T:System.Array"></see> must have zero-based indexing.</param>
		/// <param name="arrayIndex">The zero-based index in array at which copying begins.</param>
		/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="arrayIndex"/> is less than 0.</exception>
		/// <exception cref="T:System.ArgumentNullException">array is null.</exception>
		/// <exception cref="T:System.ArgumentException">array is multidimensional.-or-<paramref name="arrayIndex"/> is equal to or greater than the length of array.-or-The number of elements in the source <see cref="T:System.Collections.Generic.ICollection`1"></see> is greater than the available space from <paramref name="arrayIndex"/> to the end of the destination array.-or-Type T cannot be cast automatically to the type of the destination array.</exception>
		public void CopyTo(T[] array, int arrayIndex)
		{
			if (CollectionTyped)
			{
				((ICollection<T>)this).CopyTo(array, arrayIndex);
			}
			else if (Collection)
			{
				((ICollection)this).CopyTo(array, arrayIndex);
			}
			else
			{
				throw new NotSupportedException(
					string.Format(
						"Underlaying collection does not exposes either ICollection<{0}> or ICollection interfaces",
						typeof(T).Name));
			}
		}

		/// <summary>
		/// Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1"></see>.
		/// </summary>
		/// <returns>
		/// true if item was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1"></see>; otherwise, false. This method also returns false if item is not found in the original <see cref="T:System.Collections.Generic.ICollection`1"></see>.
		/// </returns>
		/// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1"></see>.</param>
		/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only.</exception>
		public void Remove(T item)
		{
			if (CollectionTyped)
			{
				((ICollection<T>)this).Remove(item);
			}
			else if (List)
			{
				((IList)this).Remove(item);
			}
			else
			{
				throw new NotSupportedException(
					string.Format(
						"Underlaying collection does not exposes either ICollection<{0}> or IList interfaces",
						typeof(T).Name));
			}
		}

		/// <summary>
		/// Determines the index of a specific item in the <see cref="T:System.Collections.Generic.IList`1"></see>.
		/// </summary>
		/// <returns>
		/// The index of item if found in the list; otherwise, -1.
		/// </returns>
		/// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.IList`1"></see>.</param>
		public int IndexOf(T item)
		{
			if (ListTyped)
			{
				return ((IList<T>)this).IndexOf(item);
			}
			if (List)
			{
				return ((IList)this).IndexOf(item);
			}

			throw new NotSupportedException(
				string.Format("Underlaying collection does not exposes either IList<{0}> or IList interfaces",
					typeof(T).Name));
		}

		/// <summary>
		/// Inserts an item to the <see cref="T:System.Collections.Generic.IList`1"></see> at the specified index.
		/// </summary>
		/// <param name="item">The object to insert into the <see cref="T:System.Collections.Generic.IList`1"></see>.</param>
		/// <param name="index">The zero-based index at which item should be inserted.</param>
		/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IList`1"></see> is read-only.</exception>
		/// <exception cref="T:System.ArgumentOutOfRangeException">index is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"></see>.</exception>
		public void Insert(int index, T item)
		{
			if (ListTyped)
			{
				((IList<T>)this).Insert(index, item);
			}
			else if (List)
			{
				((IList)this).Insert(index, item);
			}
			else
			{
				throw new NotSupportedException(
					string.Format("Underlaying collection does not exposes either IList<{0}> or IList interfaces",
						typeof(T).Name));
			}
		}

		/// <summary>
		/// Removes the <see cref="T:System.Collections.Generic.IList`1"></see> item at the specified index.
		/// </summary>
		/// <param name="index">The zero-based index of the item to remove.</param>
		/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IList`1"></see> is read-only.</exception>
		/// <exception cref="T:System.ArgumentOutOfRangeException">index is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"></see>.</exception>
		public void RemoveAt(int index)
		{
			if (ListTyped)
			{
				((IList<T>)this).RemoveAt(index);
			}
			else if (List)
			{
				((IList)this).RemoveAt(index);
			}
			else
			{
				throw new NotSupportedException(
					string.Format("Underlaying collection does not exposes either IList<{0}> or IList interfaces",
						typeof(T).Name));
			}
		}

		/// <summary>
		/// Gets or sets the element at the specified index.
		/// </summary>
		/// <returns>
		/// The element at the specified index.
		/// </returns>
		/// <param name="index">The zero-based index of the element to get or set.</param>
		/// <exception cref="T:System.ArgumentOutOfRangeException">index is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"></see>.</exception>
		/// <exception cref="T:System.NotSupportedException">The property is set and the <see cref="T:System.Collections.Generic.IList`1"></see> is read-only.</exception>
		public virtual T this[int index]
		{
			get
			{
				if (ListTyped)
				{
					return ((IList<T>)this)[index];
				}
				if (List)
				{
					return (T)((IList)this)[index];
				}

				throw new NotSupportedException(
					string.Format("Underlaying collection does not exposes either IList<{0}> or IList interfaces",
						typeof(T).Name));
			}
			set
			{
				if (ListTyped)
				{
					((IList<T>)this)[index] = value;
				}
				else if (List)
				{
					((IList)this)[index] = value;
				}
				else
				{
					throw new NotSupportedException(
						string.Format(
							"Underlaying collection does not exposes either IList<{0}> or IList interfaces",
							typeof(T).Name));
				}
			}
		}

		#endregion

		#region IList<T> Members

		T IList<T>.this[int index]
		{
			get { return this._asListTyped[index]; }
			set { this._asListTyped[index] = value; }
		}

		int IList<T>.IndexOf(T item)
		{
			return _asListTyped.IndexOf(item);
		}

		void IList<T>.Insert(int index, T item)
		{
			_asListTyped.Insert(index, item);
		}

		void IList<T>.RemoveAt(int index)
		{
			_asListTyped.RemoveAt(index);
		}

		#endregion

		#region ICollection<T> Members

		int ICollection<T>.Count
		{
			get { return this._asCollectionTyped.Count; }
		}

		bool ICollection<T>.IsReadOnly
		{
			get { return this._asCollectionTyped.IsReadOnly; }
		}

		void ICollection<T>.Add(T item)
		{
			_asCollectionTyped.Add(item);
		}

		void ICollection<T>.Clear()
		{
			_asCollectionTyped.Clear();
		}

		bool ICollection<T>.Contains(T item)
		{
			return _asCollectionTyped.Contains(item);
		}

		void ICollection<T>.CopyTo(T[] array, int arrayIndex)
		{
			_asCollectionTyped.CopyTo(array, arrayIndex);
		}

		bool ICollection<T>.Remove(T item)
		{
			return _asCollectionTyped.Remove(item);
		}

		#endregion

		#region IEnumerable<T> Members

		IEnumerator<T> IEnumerable<T>.GetEnumerator()
		{
			if (EnumerableTyped)
			{
				return _asEnumerableTyped.GetEnumerator();
			}

			return new AutoCastEnumerator(((IEnumerable)this).GetEnumerator());
		}

		#endregion

		#region IList Members

		/// <summary>
		/// Gets a value indicating whether the <see cref="T:System.Collections.IList"></see> has a fixed size.
		/// </summary>
		/// <returns>
		/// true if the <see cref="T:System.Collections.IList"></see> has a fixed size; otherwise, false.
		/// </returns>
		/// <filterpriority>2</filterpriority>
		bool IList.IsFixedSize
		{
			get { return this._asList.IsFixedSize; }
		}

		bool IList.IsReadOnly
		{
			get { return this._asList.IsReadOnly; }
		}

		/// <summary>
		/// Gets or sets the element at the specified index.
		/// </summary>
		/// <returns>
		/// The element at the specified index.
		/// </returns>
		/// <param name="index">The zero-based index of the element to get or set. </param>
		/// <exception cref="T:System.ArgumentOutOfRangeException">index is not a valid index in the <see cref="T:System.Collections.IList"></see>. </exception>
		/// <exception cref="T:System.NotSupportedException">The property is set and the <see cref="T:System.Collections.IList"></see> is read-only. </exception><filterpriority>2</filterpriority>
		object IList.this[int index]
		{
			get { return this._asList[index]; }
			set { this._asList[index] = value; }
		}

		/// <summary>
		/// Adds an item to the <see cref="T:System.Collections.IList"></see>.
		/// </summary>
		/// <returns>
		/// The position into which the new element was inserted.
		/// </returns>
		/// <param name="value">The <see cref="T:System.Object"></see> to add to the <see cref="T:System.Collections.IList"></see>. </param>
		/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.IList"></see> is read-only.-or- The <see cref="T:System.Collections.IList"></see> has a fixed size. </exception><filterpriority>2</filterpriority>
		int IList.Add(object value)
		{
			return _asList.Add(value);
		}

		/// <summary>
		/// Determines whether the <see cref="T:System.Collections.IList"></see> contains a specific value.
		/// </summary>
		/// <returns>
		/// true if the <see cref="T:System.Object"></see> is found in the <see cref="T:System.Collections.IList"></see>; otherwise, false.
		/// </returns>
		/// <param name="value">The <see cref="T:System.Object"></see> to locate in the <see cref="T:System.Collections.IList"></see>. </param><filterpriority>2</filterpriority>
		bool IList.Contains(object value)
		{
			return _asList.Contains(value);
		}

		void IList.Clear()
		{
			_asList.Clear();
		}

		/// <summary>
		/// Determines the index of a specific item in the <see cref="T:System.Collections.IList"></see>.
		/// </summary>
		/// <returns>
		/// The index of value if found in the list; otherwise, -1.
		/// </returns>
		/// <param name="value">The <see cref="T:System.Object"></see> to locate in the <see cref="T:System.Collections.IList"></see>. </param><filterpriority>2</filterpriority>
		int IList.IndexOf(object value)
		{
			return _asList.IndexOf(value);
		}

		/// <summary>
		/// Inserts an item to the <see cref="T:System.Collections.IList"></see> at the specified index.
		/// </summary>
		/// <param name="value">The <see cref="T:System.Object"></see> to insert into the <see cref="T:System.Collections.IList"></see>. </param>
		/// <param name="index">The zero-based index at which value should be inserted. </param>
		/// <exception cref="T:System.ArgumentOutOfRangeException">index is not a valid index in the <see cref="T:System.Collections.IList"></see>. </exception>
		/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.IList"></see> is read-only.-or- The <see cref="T:System.Collections.IList"></see> has a fixed size. </exception>
		/// <exception cref="T:System.NullReferenceException">value is null reference in the <see cref="T:System.Collections.IList"></see>.</exception><filterpriority>2</filterpriority>
		void IList.Insert(int index, object value)
		{
			_asList.Insert(index, value);
		}

		/// <summary>
		/// Removes the first occurrence of a specific object from the <see cref="T:System.Collections.IList"></see>.
		/// </summary>
		/// <param name="value">The <see cref="T:System.Object"></see> to remove from the <see cref="T:System.Collections.IList"></see>. </param>
		/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.IList"></see> is read-only.-or- The <see cref="T:System.Collections.IList"></see> has a fixed size. </exception><filterpriority>2</filterpriority>
		void IList.Remove(object value)
		{
			_asList.Remove(value);
		}

		void IList.RemoveAt(int index)
		{
			_asList.RemoveAt(index);
		}

		#endregion

		#region ICollection Members

		int ICollection.Count
		{
			get { return this._asCollection.Count; }
		}

		/// <summary>
		/// Gets a value indicating whether access to the <see cref="T:System.Collections.ICollection"></see> is synchronized (thread safe).
		/// </summary>
		/// <returns>
		/// true if access to the <see cref="T:System.Collections.ICollection"></see> is synchronized (thread safe); otherwise, false.
		/// </returns>
		/// <filterpriority>2</filterpriority>
		bool ICollection.IsSynchronized
		{
			get { return this._asCollection.IsSynchronized; }
		}

		/// <summary>
		/// Gets an object that can be used to synchronize access to the <see cref="T:System.Collections.ICollection"></see>.
		/// </summary>
		/// <returns>
		/// An object that can be used to synchronize access to the <see cref="T:System.Collections.ICollection"></see>.
		/// </returns>
		/// <filterpriority>2</filterpriority>
		object ICollection.SyncRoot
		{
			get { return this._asCollection.SyncRoot; }
		}

		/// <summary>
		/// Copies the elements of the <see cref="T:System.Collections.ICollection"></see> to an <see cref="T:System.Array"></see>, starting at a particular <see cref="T:System.Array"></see> index.
		/// </summary>
		/// <param name="array">The one-dimensional <see cref="T:System.Array"></see> that is the destination of the elements copied from <see cref="T:System.Collections.ICollection"></see>. The <see cref="T:System.Array"></see> must have zero-based indexing. </param>
		/// <param name="index">The zero-based index in array at which copying begins. </param>
		/// <exception cref="T:System.ArgumentNullException">array is null. </exception>
		/// <exception cref="T:System.ArgumentOutOfRangeException">index is less than zero. </exception>
		/// <exception cref="T:System.ArgumentException">array is multidimensional.-or- index is equal to or greater than the length of array.-or- The number of elements in the source <see cref="T:System.Collections.ICollection"></see> is greater than the available space from index to the end of the destination array. </exception>
		/// <exception cref="T:System.InvalidCastException">The type of the source <see cref="T:System.Collections.ICollection"></see> cannot be cast automatically to the type of the destination array. </exception><filterpriority>2</filterpriority>
		void ICollection.CopyTo(Array array, int index)
		{
			_asCollection.CopyTo(array, index);
		}

		#endregion

		#region IEnumerable Members

		/// <summary>
		/// Returns an enumerator that iterates through a collection.
		/// </summary>
		/// <returns>
		/// An <see cref="T:System.Collections.IEnumerator"></see> object that can be used to iterate through the collection.
		/// </returns>
		/// <filterpriority>2</filterpriority>
		IEnumerator IEnumerable.GetEnumerator()
		{
			return _asEnumerable.GetEnumerator();
		}

		#endregion

		#region Overrides of Disposable

		/// <summary>
		/// Освобождает ресурсы класса наследника.
		/// </summary>
		/// <param name="disposing"><c>true</c> - если был вызван метод Dispose(), 
		/// <c>false</c> - в противном случае.</param>
		protected override void DisposeOnce(bool disposing)
		{
			if (disposing)
			{
				_underlayingCollection = null;
			}
		}

		#endregion
	}
}